// outliner.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "outliner.h"
#include <CommDlg.h>
#include <CommCtrl.h>
#include "Picture.h"
#include "FilePath.h"

// should be last include
#define STRSAFE_LIB
#include <strsafe.h>

// constants
#define MAX_LOADSTRING 100
const TCHAR* kFilterBmp = _T("Bitmap images (*.bmp)\0*.bmp\0\0");
const TCHAR* kSuffixOfWindowTitle = _T(" - outliner");

// the file formats here should correspond to OutlinesFilterIndex enumeration.
const TCHAR* kFilterOutlines = 
	_T("Scalable Vector Graphics (*.svg)\0*.svg\0")	
	_T("Vector Markup Language (*.htm)\0*.htm\0")
	_T("Portable Document Format (*.pdf)\0*.pdf\0")
	_T("Enhanced Metafile Format EMF+ (*.emf)\0*.emf\0\0");



// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name
Logger gLogger;
Picture gPicture(gLogger);
FilePath gFilePath;
ThreadRunner gThread(gPicture);

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: Place code here.
	MSG msg;
	HACCEL hAccelTable;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_OUTLINER, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_OUTLINER));

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage are only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_OUTLINER));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_OUTLINER);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, 
	   WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL | WS_CLIPCHILDREN,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   INITCOMMONCONTROLSEX icce;
   icce.dwSize = sizeof(icce);
   icce.dwICC = ICC_BAR_CLASSES;
   ::InitCommonControlsEx(&icce);

   HWND statusWnd = ::CreateWindowEx(0, STATUSCLASSNAME, _T("statusbar"),
	   WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0, hWnd, 
	   (HMENU)2000, hInst, NULL);

   if (HMENU hmenu = ::GetMenu(hWnd))
   {
		int state = (NULL == statusWnd ? MF_UNCHECKED : MF_CHECKED);
		::CheckMenuItem(hmenu, IDM_SHOWSTATUSBAR, state | MF_BYCOMMAND);
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}


int DoScroll(HWND inHWnd, WPARAM inWParam, int inScrollbarID)
{
	SCROLLINFO scrollInfo;
	scrollInfo.cbSize = sizeof(scrollInfo);
	scrollInfo.fMask = SIF_ALL;
	::GetScrollInfo(inHWnd, inScrollbarID, &scrollInfo);

	int pos = scrollInfo.nPos;
	switch (LOWORD(inWParam))
	{
	case SB_PAGEUP: pos -= scrollInfo.nPage; break;
	case SB_PAGEDOWN: pos += scrollInfo.nPage; break;
	case SB_LINEUP: pos -= 10; break;
	case SB_LINEDOWN: pos += 10; break;
	case SB_THUMBPOSITION: pos = HIWORD(inWParam); break;
	}
	int disableOption = 0;
	if (pos < 0) 
	{
		pos = 0; 
		disableOption |= ESB_DISABLE_UP;
	}
	int maxPos = scrollInfo.nMax - scrollInfo.nPage;
	if (pos > maxPos && maxPos > 0) 
	{
		pos = maxPos;
		disableOption |= ESB_DISABLE_DOWN;
	}

	int delta = pos - scrollInfo.nPos;
	if (delta)
	{
		::SetScrollPos(inHWnd, inScrollbarID, pos, true);
		::EnableScrollBar(inHWnd, inScrollbarID, disableOption);
	}
	return delta;
}

double OldProportion(HWND inHWnd, SCROLLINFO& outScrollInfo, int inScrollbarID)
{
	outScrollInfo.fMask = SIF_ALL;
	::GetScrollInfo(inHWnd, inScrollbarID, &outScrollInfo);
	if (0 == outScrollInfo.nMax)
	{
		return 0;
	}
	return (outScrollInfo.nPos + 0.5 * outScrollInfo.nPage) / outScrollInfo.nMax;
}

void AdjustPosition(SCROLLINFO& ioScrollInfo)
{
	if (ioScrollInfo.nPos <= 0)
	{
		ioScrollInfo.nPos = 0;
	}
	int maxPos = max(ioScrollInfo.nMax - ioScrollInfo.nPage, 0);
	if (ioScrollInfo.nPos >= maxPos)
	{
		ioScrollInfo.nPos = maxPos;
	}
}

void SetCurrentScrollInfo(HWND inHWnd, bool inKeepPosition)
{
	SCROLLINFO scrollInfo;
	scrollInfo.cbSize = sizeof(scrollInfo);

	int x = 0, y = 0;
	int width = gPicture.GetWidth();
	int height = gPicture.GetHeight();

	RECT rect;
	::GetClientRect(inHWnd, &rect);
	int clientWidth = rect.right - rect.left;
	int clientHeight = rect.bottom - rect.top;

	if (HWND statusBar = ::FindWindowEx(inHWnd, NULL, STATUSCLASSNAME, NULL))
	{
		RECT statusRect;
		if (::GetWindowRect(statusBar, &statusRect))
		{
			clientHeight -= statusRect.bottom - statusRect.top;
		}
	}


	if (inKeepPosition)
	{
		x = int(width * OldProportion(inHWnd, scrollInfo, SB_HORZ) - 0.5 * clientWidth);
		y = int(height * OldProportion(inHWnd, scrollInfo, SB_VERT) - 0.5 * clientHeight);
	}
	scrollInfo.nMin = 0;
	scrollInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;

	int disableFlags = ESB_DISABLE_BOTH;
	if (clientWidth < width)
	{
		scrollInfo.nPos = x;
		scrollInfo.nPage = clientWidth;
		scrollInfo.nMax = width;
		AdjustPosition(scrollInfo);
		::SetScrollInfo(inHWnd, SB_HORZ, &scrollInfo, true);
		disableFlags = ESB_ENABLE_BOTH;
	}
	else
	{
		::SetScrollPos(inHWnd, SB_HORZ, 0, true);
	}
	::EnableScrollBar(inHWnd, SB_HORZ, disableFlags);

	if (clientHeight < height)
	{
		scrollInfo.nPos = y;
		scrollInfo.nPage = clientHeight;
		scrollInfo.nMax = height;
		AdjustPosition(scrollInfo);
		::SetScrollInfo(inHWnd, SB_VERT, &scrollInfo, true);
		disableFlags = ESB_ENABLE_BOTH;
	}
	else
	{
		::SetScrollPos(inHWnd, SB_VERT, 0, true);
	}
	::EnableScrollBar(inHWnd, SB_VERT, disableFlags);
}

void Restart()
{
	if (0 < gPicture.GetWidth() && 0 < gPicture.GetHeight())
	{
		gThread.Stop(); 
		gPicture.GetSketch().ResetSketch();
		gThread.Start();
	}
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	LRESULT result = 0;

	switch (message)
	{
	case WM_COMMAND:
		{
			wmId    = LOWORD(wParam);
			wmEvent = HIWORD(wParam);
			bool oldCanSave = gPicture.CanSaveBitmap();

			// Parse the menu selections:
			switch (wmId)
			{
			case IDM_ABOUT:
				DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
				break;
			case IDM_OPEN:
				{
					TCHAR fileName[1000];
					TCHAR title[200];

					OPENFILENAME ofn;
					ZeroMemory(&ofn, sizeof(ofn));
					ofn.lStructSize = sizeof(ofn);
					ofn.hwndOwner = hWnd;
					ofn.lpstrFilter = 
						_T("Images only\0*.bmp;*.jpg;*.gif;*.tif;*.png\0All files\0*.*\0\0");
						//kFilterBmp;
					ofn.nFilterIndex = 1;

					ofn.lpstrFile = fileName;
					// Set lpstrFile[0] to '\0' so that GetOpenFileName does not 
					// use the contents of szFile to initialize itself.
					ofn.lpstrFile[0] = '\0';
					ofn.nMaxFile = sizeof(fileName)/sizeof(fileName[0]);

					ofn.lpstrFileTitle = title;
					size_t suffixLength;
					HRESULT hr = ::StringCbLength(kSuffixOfWindowTitle, 100, &suffixLength);
					ofn.nMaxFileTitle = sizeof(title) / sizeof(title[0]) -
						(suffixLength + 1);

					ofn.Flags = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_HIDEREADONLY;
					BOOL ok = ::GetOpenFileName(&ofn);
					if (ok)
					{
						// accept file
						SCROLLINFO scrollInfo;
						scrollInfo.cbSize = sizeof(scrollInfo);

						static class PictureEventHandler : public Picture::EventHandler
						{
						public:
							PictureEventHandler(HWND hWnd)
								: mhWnd(hWnd)
							{}

							virtual void NotifyChange(int inNotificationID) const
							{
								if (mhWnd)
								{
									::PostMessage(mhWnd, inNotificationID, /*inWParam*/ 0, 0);
								}
							}
						private:
							HWND mhWnd;
						} pictureEventHandler(hWnd);
						gPicture.SetEventHandler(&pictureEventHandler);
						if (gPicture.Load(fileName))
						{
							gThread.Start();
							::EnableMenuItem(::GetMenu(hWnd), IDM_SAVEOUTLINES, 
								MF_GRAYED | MF_BYCOMMAND);
							gFilePath.SetPathToOriginal(fileName);
							SetCurrentScrollInfo(hWnd, false);

							// change the window title
							hr = ::StringCbCat(title, sizeof(title), kSuffixOfWindowTitle);
							if (SUCCEEDED(hr))
							{
								::SetWindowText(hWnd, title);
							}
						}

						::InvalidateRect(hWnd, NULL, false);
					}
				}
				break;
			case IDM_SAVETO:
				{
					TCHAR fileName[1024] = _T("");

					OPENFILENAME fileNameStruct;
					::memset(&fileNameStruct, 0, sizeof(fileNameStruct));
					fileNameStruct.lStructSize = sizeof(fileNameStruct);
					fileNameStruct.hwndOwner = hWnd;
					fileNameStruct.hInstance = NULL;
					fileNameStruct.lpstrFilter = kFilterBmp;
					fileNameStruct.lpstrCustomFilter = NULL;
					fileNameStruct.nFilterIndex = 1;
					fileNameStruct.lpstrFile = fileName;
					fileNameStruct.nMaxFile = sizeof(fileName) / sizeof(fileName[0]);
					fileNameStruct.Flags = OFN_OVERWRITEPROMPT;
					fileNameStruct.lpstrDefExt = _T("bmp");
					gFilePath.GetFileNameToSave(fileName, fileNameStruct.nMaxFile, true, gPicture.GetView());
					BOOL ok = ::GetSaveFileName(&fileNameStruct);
					if (ok)
					{
						gPicture.SaveCurrentViewTo(fileName);
					}
				}
				break;
			case IDM_SAVEOUTLINES:
				{
					TCHAR fileName[1024] = _T("");

					OPENFILENAME fileNameStruct;
					::memset(&fileNameStruct, 0, sizeof(fileNameStruct));
					fileNameStruct.lStructSize = sizeof(fileNameStruct);
					fileNameStruct.hwndOwner = hWnd;
					fileNameStruct.hInstance = NULL;
					fileNameStruct.lpstrFilter = kFilterOutlines;
					fileNameStruct.lpstrCustomFilter = NULL;
					fileNameStruct.nFilterIndex = 1;
					fileNameStruct.lpstrFile = fileName;
					fileNameStruct.nMaxFile = sizeof(fileName) / sizeof(fileName[0]);
					fileNameStruct.Flags = OFN_OVERWRITEPROMPT;
					fileNameStruct.lpstrDefExt = _T("svg");
					fileNameStruct.lpstrTitle = _T("Save outlines");
					gFilePath.GetFileNameToSave(fileName, fileNameStruct.nMaxFile, false, gPicture.GetView());
					BOOL ok = ::GetSaveFileName(&fileNameStruct);
					if (ok)
					{
						ok = ok;
						gPicture.GetSketch().SaveOutlinesTo(fileName, fileNameStruct.nFilterIndex);
					}
				}
				break;
			case IDM_SHOWSTATUSBAR:
				{
					if (HWND statusHWnd = ::FindWindowEx(hWnd, 0, STATUSCLASSNAME, NULL))
					{
						HMENU hmenu = ::GetMenu(hWnd);
						int state = ::GetMenuState(hmenu, IDM_SHOWSTATUSBAR, MF_BYCOMMAND);
						if (-1 != state)
						{
							int newState = (state & MF_CHECKED) ^ MF_CHECKED;
							int st1 = (newState == MF_CHECKED ? SW_SHOW : SW_HIDE);
							::ShowWindow(statusHWnd, st1);
							::CheckMenuItem(hmenu, IDM_SHOWSTATUSBAR, newState | MF_BYCOMMAND);
						}
					}
				}
				break;

			case IDM_SHOWLOG:
				{
					::MessageBox(hWnd, gLogger.GetLogText().c_str(), _T("Performance log"), MB_OK);
				}
				break;

			case IDM_SHOWSTATISTICS:
				{
					::MessageBox(hWnd, gPicture.GetSketch().GetStatisticsText().c_str(), 
						_T("Statistics"), MB_OK);
				}
				break;

			case IDM_EXIT:
				DestroyWindow(hWnd);
				break;
			default:
				{
					HMENU hMenu = ::GetMenu(hWnd);
					if (IDM_SCALE50 <= wmId && wmId <= IDM_SCALE1100)
					{
						float kScales[IDM_SCALE1100 - IDM_SCALE50 + 1] = 
							{0.5f, 1, 2, 5, 10, 20, 50};
						gPicture.SetScale(kScales[wmId - IDM_SCALE50]);
						if (hWnd)
							::InvalidateRect(hWnd, NULL, true);
						SetCurrentScrollInfo(hWnd, true);
						::CheckMenuRadioItem(hMenu, IDM_SCALE50,
							IDM_SCALE1100, wmId, MF_BYCOMMAND);
						::InvalidateRect(hWnd, NULL, false);
					}
					else if (IDM_NORMAL <= wmId && wmId <= IDM_BiggestViewAspect)
					{
						gPicture.SelectViewAspect((ViewID)(wmId - IDM_NORMAL));
						::InvalidateRect(hWnd, NULL, false);

						::CheckMenuRadioItem(hMenu, IDM_NORMAL,
							IDM_BiggestViewAspect, wmId, MF_BYCOMMAND);
					}
					else if (IDM_SHOWCURVES <= wmId && wmId <= IDM_SHOWALLDISTINCTIVEPOINTS)
					{
						int oldState = ::GetMenuState(hMenu, wmId, MF_BYCOMMAND);
						int newState = (oldState & MF_CHECKED) ^ MF_CHECKED;
						bool wasDrawingAllStrokes = gPicture.GetSketch().IsDrawingAllStrokes();
						gPicture.GetSketch().SetVectorParameter(wmId - IDM_SHOWCURVES,
							newState == MF_CHECKED);
						::CheckMenuItem(hMenu, wmId, newState | MF_BYCOMMAND);
						if (wasDrawingAllStrokes || gPicture.GetSketch().IsDrawingAllStrokes() ||
							IDM_SHOWALLTHICKCURVES == wmId ||
							IDM_SHOWALLDISTINCTIVEPOINTS == wmId)
						{
							::InvalidateRect(hWnd, NULL, false);
						}
					}
					else if (IDM_BLUR1 <= wmId && wmId <= IDM_BLUR10)
					{
						float blur[] = {1.0f, 1.5f, 2.0f, 3.0f, 5.0f, 8.0f};
						if(gPicture.SetBlur(blur[wmId - IDM_BLUR1]))
						{
							Restart();
						}
						::CheckMenuRadioItem(hMenu, IDM_BLUR1,
							IDM_BLUR10, wmId, MF_BYCOMMAND);
					}
					else if (IDM_THRESHOLD0_05 <= wmId && wmId <= IDM_THRESHOLD0_5)
					{
						float threshold[] = {0.005f, 0.1f, 0.2f, 0.3f, 0.5f};
						if(gPicture.SetThreshold(threshold[wmId - IDM_THRESHOLD0_05]))
						{
							Restart();
						}
						::CheckMenuRadioItem(hMenu, IDM_THRESHOLD0_05,
							IDM_THRESHOLD0_5, wmId, MF_BYCOMMAND);
					}
					else if (IDM_NOISE0_0 <= wmId && wmId <= IDM_NOISE1_0)
					{
						float noise[] = {0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f};
						if(gPicture.SetNoise(noise[wmId - IDM_NOISE0_0]))
						{
							Restart();
						}
						::CheckMenuRadioItem(hMenu, IDM_NOISE0_0,
							IDM_NOISE1_0, wmId, MF_BYCOMMAND);
					}
					else if (IDM_CLUTTER0_0 <= wmId && wmId <= IDM_CLUTTER1_0)
					{
						float clutterSuppression[] = 
							{0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f};
						if(gPicture.SetClutterSuppression(
							clutterSuppression[wmId - IDM_CLUTTER0_0]))
						{
							Restart();
						}
						::CheckMenuRadioItem(hMenu, IDM_CLUTTER0_0,
							IDM_CLUTTER1_0, wmId, MF_BYCOMMAND);
					}
					else if (IDM_CURVATURE_NONE <= wmId &&
						wmId <= IDM_CURVATURE_BEST)
					{
						::CheckMenuRadioItem(hMenu, IDM_CURVATURE_NONE,
							IDM_CURVATURE_BEST, wmId, MF_BYCOMMAND);
						int curvatureToken = wmId - IDM_CURVATURE_NONE;
						if (gPicture.SetCurvatureCalculationToken(curvatureToken))
						{
							Restart();
						}
					}
				}
				result = DefWindowProc(hWnd, message, wParam, lParam);
			}
			bool newCanSave = gPicture.CanSaveBitmap();
			if (newCanSave != oldCanSave)
			{
				UINT enabled = newCanSave ? MF_ENABLED : MF_GRAYED;
				::EnableMenuItem(::GetMenu(hWnd), IDM_SAVETO, enabled | MF_BYCOMMAND);
			}
		}
		break;
	case WM_PAINT:
		{
			if (GetUpdateRect(hWnd, NULL, false))
			{
				hdc = BeginPaint(hWnd, &ps);
				int dx = ::GetScrollPos(hWnd, SB_HORZ);
				int dy = ::GetScrollPos(hWnd, SB_VERT);
				::SetViewportOrgEx(hdc, -dx, -dy, NULL);
				RECT rect = ps.rcPaint;
				::OffsetRect(&rect, dx, dy);
				gPicture.Draw(hdc, rect);
				EndPaint(hWnd, &ps);
			}
		}
		break;
	
	case WM_HSCROLL:
		{
			if (int deltaX = DoScroll(hWnd, wParam, SB_HORZ))
			{
				::ScrollWindowEx(hWnd, -deltaX, 0, NULL, NULL, NULL,
					NULL, SW_INVALIDATE);
				::UpdateWindow(hWnd);
			}
		}
		break;
	case WM_VSCROLL:
		{
			if (int deltaY = DoScroll(hWnd, wParam, SB_VERT))
			{
				::ScrollWindowEx(hWnd, 0, -deltaY, NULL, NULL, NULL,
					NULL, SW_INVALIDATE);
				::UpdateWindow(hWnd);
			}
		}
		break;
	case WM_SIZE:
		{
			SetCurrentScrollInfo(hWnd, true);
			if (HWND statusBar = ::FindWindowEx(hWnd, NULL, STATUSCLASSNAME, NULL))
			{
				::SendMessage(statusBar, WM_SIZE, wParam, lParam);
			}
		}
		break;

	case WM_MOUSEMOVE:
		{
			static int entries = 0;
			if (entries) break;
			++entries;
			int xShift = ::GetScrollPos(hWnd, SB_HORZ);
			int yShift = ::GetScrollPos(hWnd, SB_VERT);
			int x = LOWORD(lParam) + xShift;
			int y = HIWORD(lParam) + yShift;
			RECT rect;
			if (gPicture.GetSketch().HighlightStroke(gPicture, x, y, rect))
			{
				::OffsetRect(&rect, -xShift, -yShift);
				::InflateRect(&rect, 5,5);
				::InvalidateRect(hWnd, &rect, false);
			}
			std::wstring s = gLogger.PixelPositionStr(x, y, gPicture);
			if (!s.empty())
			{
				if (HWND statusBar = ::FindWindowEx(hWnd, NULL, STATUSCLASSNAME, NULL))
				{
					::SetWindowText(statusBar, s.c_str());
				}
			}
			--entries;
		}
		break;
	case WM_LBUTTONDOWN:
		{
		}
		break;
	case WM_USER_UPDATECLIENT:
		{
			::InvalidateRect(hWnd, NULL, false);
		}
		break;

	case WM_USER_UPDATESTATUSBAR:
		{
			if (HWND statusBar = ::FindWindowEx(hWnd, NULL, STATUSCLASSNAME, NULL))
			{
				::SetWindowText(statusBar, gLogger.GetStatusText().c_str());
			}
			UINT enabled = gPicture.GetSketch().CanSaveOutlines() ? MF_ENABLED : MF_GRAYED;
			::EnableMenuItem(::GetMenu(hWnd), IDM_SAVEOUTLINES, 
								enabled | MF_BYCOMMAND);
		}
		break;

	case WM_CREATE:
		{
			LRESULT result = ::DefWindowProc(hWnd, message, wParam, lParam);
			gPicture.SetScale(1);
			if (hWnd)
				::InvalidateRect(hWnd, NULL, true);
			HMENU hMenu = ::GetMenu(hWnd);
			::CheckMenuRadioItem(hMenu, IDM_SCALE50, IDM_SCALE1100, 
				IDM_SCALE100, MF_BYCOMMAND);
			::CheckMenuRadioItem(hMenu, IDM_NORMAL, IDM_BiggestViewAspect,
				IDM_NORMAL, MF_BYCOMMAND);
			::CheckMenuRadioItem(hMenu, IDM_BLUR1,
							IDM_BLUR10, IDM_BLUR2, MF_BYCOMMAND);
			gPicture.SetBlur(1.5f);

			::CheckMenuRadioItem(hMenu, IDM_THRESHOLD0_05,
							IDM_THRESHOLD0_5, IDM_THRESHOLD0_1, MF_BYCOMMAND);
			gPicture.SetThreshold(0.1f);

			::CheckMenuRadioItem(hMenu, IDM_NOISE0_0,
							IDM_NOISE1_0, IDM_NOISE0_4, MF_BYCOMMAND);
			gPicture.SetNoise(0.4f);

			::CheckMenuItem(hMenu, IDM_SHOWDOTCENTERS, MF_CHECKED | MF_BYCOMMAND);
			gPicture.GetSketch().SetVectorParameter(IDM_SHOWDOTCENTERS - IDM_SHOWCURVES, true);

			::CheckMenuRadioItem(hMenu, IDM_CLUTTER0_0,
							IDM_CLUTTER1_0, IDM_CLUTTER0_2, MF_BYCOMMAND);
			gPicture.SetClutterSuppression(0.2f);

			::CheckMenuRadioItem(hMenu, IDM_CURVATURE_NONE,
							IDM_CURVATURE_BEST, IDM_CURVATURE_BEST, MF_BYCOMMAND);
			gPicture.SetCurvatureCalculationToken(IDM_CURVATURE_BEST - IDM_CURVATURE_NONE);

			return result;
		} 
		break;
	case WM_DESTROY:
		gThread.Stop();
		PostQuitMessage(0);
		break;
	default:
		result = DefWindowProc(hWnd, message, wParam, lParam);
	}
	return result;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}
